// 判断子节点是否为空节点
export function isEmptyElement(c) {
    return !(c.tag || (c.text && c.text.trim() !== ''));
}

// String node
export function isStringElement(c) {
    return !c.tag;
}

// 过滤空节点
export function filterEmpty(children = []) {
    return children.filter(c => !isEmptyElement(c));
}

export function getAllChildren(ele) {
    let componentOptions = ele.componentOptions || {};
    if (ele.$vnode) {
        componentOptions = ele.$vnode.componentOptions || {};
    }
    const c = ele.children || componentOptions.children || []
    //过滤插槽
    return c.filter(r => !(r.data && r.data.slot))
}
// 根据名称获取插槽: slots 、scopeSlots
export function getSlot(self, name = 'default', options = {}) {
    return (
        (self.$scopedSlots && self.$scopedSlots[name] && self.$scopedSlots[name](options)) ||
        self.$slots[name] ||
        []
    );
}

// 或所有插槽
export function getSlots(ele) {
    let componentOptions = ele.componentOptions || {};
    if (ele.$vnode) {
        componentOptions = ele.$vnode.componentOptions || {};
    }
    const children = ele.children || componentOptions.children || [];
    const slots = {};
    children.forEach(child => {
        if (!isEmptyElement(child)) {
            const name = (child.data && child.data.slot) || 'default';
            slots[name] = slots[name] || [];
            slots[name].push(child);
        }
    });
    return { ...slots, ...getScopedSlots(ele) };
};

// 获取作用域插槽
export function getScopedSlots(ele) {
    return (ele.data && ele.data.scopedSlots) || {};
}

// 判断节点是传了某个属性
export function hasProp(instance, prop) {
    const $options = instance.$options || {};
    const propsData = $options.propsData || {};
    return prop in propsData;
}

// 获取所有属性数据 包括props、attrs
export function getAllProps(ele) {
    let data = ele.data || {};
    let componentOptions = ele.componentOptions || {};
    if (ele.$vnode) {
        data = ele.$vnode.data || {};
        componentOptions = ele.$vnode.componentOptions || {};
    }
    return { ...data.props, ...data.attrs, ...componentOptions.propsData };
}

// 仅仅获取prop数据
export function getPropsData(ele) {
    let componentOptions = ele.componentOptions;
    if (ele.$vnode) {
        componentOptions = ele.$vnode.componentOptions;
    }
    return componentOptions ? componentOptions.propsData || {} : {};
}

// 获取某个属性的值
export function getValueByProp(ele, prop) {
    return getPropsData(ele)[prop];
}

export function isAsyncPlaceholder(node) {
    return node.isComment && node.asyncFactory
}

export function isDef(v) {
    return v !== undefined && v !== null
}

export function getFirstComponentChild(children) {
    if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
            const c = children[i]
            if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
                return c
            }
        }
    }
}

export function isRegExp(v) {
    return _toString.call(v) === '[object RegExp]'
}

export function once(fn) {
    let called = false
    return function () {
        if (!called) {
            called = true
            fn.apply(this, arguments)
        }
    }
}

export function cloneVNode(vnode, deep) {
    const componentOptions = vnode.componentOptions;
    const data = vnode.data;

    let listeners = {};
    if (componentOptions && componentOptions.listeners) {
        listeners = { ...componentOptions.listeners };
    }

    let on = {};
    if (data && data.on) {
        on = { ...data.on };
    }

    const cloned = new vnode.constructor(
        vnode.tag,
        data ? { ...data, on } : data,
        vnode.children,
        vnode.text,
        vnode.elm,
        vnode.context,
        componentOptions ? { ...componentOptions, listeners } : componentOptions,
        vnode.asyncFactory,
    );
    cloned.ns = vnode.ns;
    cloned.isStatic = vnode.isStatic;
    cloned.key = vnode.key;
    cloned.isComment = vnode.isComment;
    cloned.fnContext = vnode.fnContext;
    cloned.fnOptions = vnode.fnOptions;
    cloned.fnScopeId = vnode.fnScopeId;
    cloned.isCloned = true;
    if (deep) {
        if (vnode.children) {
            cloned.children = cloneVNodes(vnode.children, true);
        }
        if (componentOptions && componentOptions.children) {
            componentOptions.children = cloneVNodes(componentOptions.children, true);
        }
    }
    return cloned;
}

export function cloneVNodes(vnodes, deep) {
    const len = vnodes.length;
    const res = new Array(len);
    for (let i = 0; i < len; i++) {
        res[i] = cloneVNode(vnodes[i], deep);
    }
    return res;
}

export function cloneElement(n, nodeProps = {}, deep) {
    let ele = n;
    if (Array.isArray(n)) {
        ele = filterEmpty(n)[0];
    }
    if (!ele) {
        return null;
    }
    const node = cloneVNode(ele, deep);
    const { props = {}, key, on = {}, nativeOn = {}, children, directives = [] } = nodeProps;
    const data = node.data || {};
    let cls = {};
    let style = {};
    const {
        attrs = {},
        ref,
        domProps = {},
        style: tempStyle = {},
        class: tempCls = {},
        scopedSlots = {},
    } = nodeProps;

    if (typeof data.style === 'string') {
        style = parseStyleText(data.style);
    } else {
        style = { ...data.style, ...style };
    }
    if (typeof tempStyle === 'string') {
        style = { ...style, ...parseStyleText(style) };
    } else {
        style = { ...style, ...tempStyle };
    }

    if (typeof data.class === 'string' && data.class.trim() !== '') {
        data.class.split(' ').forEach(c => {
            cls[c.trim()] = true;
        });
    } else if (Array.isArray(data.class)) {
        classNames(data.class)
            .split(' ')
            .forEach(c => {
                cls[c.trim()] = true;
            });
    } else {
        cls = { ...data.class, ...cls };
    }
    if (typeof tempCls === 'string' && tempCls.trim() !== '') {
        tempCls.split(' ').forEach(c => {
            cls[c.trim()] = true;
        });
    } else {
        cls = { ...cls, ...tempCls };
    }
    node.data = Object.assign({}, data, {
        style,
        attrs: { ...data.attrs, ...attrs },
        class: cls,
        domProps: { ...data.domProps, ...domProps },
        scopedSlots: { ...data.scopedSlots, ...scopedSlots },
        directives: [...(data.directives || []), ...directives],
    });

    if (node.componentOptions) {
        node.componentOptions.propsData = node.componentOptions.propsData || {};
        node.componentOptions.listeners = node.componentOptions.listeners || {};
        node.componentOptions.propsData = { ...node.componentOptions.propsData, ...props };
        node.componentOptions.listeners = { ...node.componentOptions.listeners, ...on };
        if (children) {
            node.componentOptions.children = children;
        }
    } else {
        if (children) {
            node.children = children;
        }
        node.data.on = { ...(node.data.on || {}), ...on };
    }
    node.data.on = { ...(node.data.on || {}), ...nativeOn };

    if (key !== undefined) {
        node.key = key;
        node.data.key = key;
    }
    if (typeof ref === 'string') {
        node.data.ref = ref;
    }
    return node;
}